package program.controller;

import javafx.animation.*;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.image.ImageView;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.CubicCurveTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.text.Text;
import javafx.util.Duration;
import program.domain.PCB;
import program.services.FirstComeFirstServed;
import program.services.RoundRobin;
import program.utils.FileUtil;
import program.utils.PCBQueue;
import program.utils.ViewUtil;

import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.ResourceBundle;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @program: OSAlgorithms
 * @description: 进程调度控制器
 * @author: W
 * @create: 2021-10-27 21:18
 **/
public class ProcessController implements Initializable {

    public static ProcessController processController;

    private FirstComeFirstServed firstComeFirstServed = new FirstComeFirstServed();

    private RoundRobin roundRobin = new RoundRobin();

    private PCB running = firstComeFirstServed.getRunningQueue().peek();

    private PCB finish = firstComeFirstServed.getFinishedQueue().peek();

    @FXML
    private VBox readyBox;

    @FXML
    private VBox finishedBox;

    @FXML
    private ImageView centerImg;

    @FXML
    private Circle runningCircle;

    @FXML
    private AnchorPane mainView;

    @FXML
    private Text timeText;

    @FXML
    private HBox msgBox;

    @FXML
    private TableView<PCB> pcbTableView;

    @FXML
    private TableColumn<PCB, Integer> pidCol;

    @FXML
    private TableColumn<PCB, Integer> arriveTimeCol;

    @FXML
    private TableColumn<PCB, Integer> needTimeCol;

    @FXML
    private TableColumn<PCB, Integer> countCol;

    @FXML
    private TableColumn<PCB, String> stateCol;

    @FXML
    private TableView<PCB> pcbTableView1;

    @FXML
    private TableColumn<PCB, Integer> pidCol1;

    @FXML
    private TableColumn<PCB, Integer> arriveTimeCol1;

    @FXML
    private TableColumn<PCB, Integer> needTimeCol1;

    @FXML
    private TableColumn<PCB, Integer> countCol1;

    @FXML
    private TableColumn<PCB, String> stateCol1;

    @FXML
    private Text needTimeText;

    private HashMap<String, Color> tagMap = new HashMap<>();

    public void mainMethod() {
        String processFileName = "process.txt";
        revolveView(centerImg);
        //FCFSScheduling(FileUtil.getProcessData(processFileName));
        RRScheduling(FileUtil.getProcessData(processFileName));
    }

    public void FCFSScheduling(ArrayList<PCB> processes) {
        firstComeFirstServed.setProcesses(processes);
        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(3);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                int time = firstComeFirstServed.getTime();
                timeText.setText(String.valueOf(time));
                firstComeFirstServed.scheduleReady();
                loadTable(firstComeFirstServed.getReadyQueue(), firstComeFirstServed.getFinishedQueue());
                readyView(firstComeFirstServed.getReadyQueue(),firstComeFirstServed.getRunningQueue());
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                firstComeFirstServed.scheduleRunning();
                updateRunningTime(firstComeFirstServed.getRunningQueue());
                removeReadyView(firstComeFirstServed.getRunningQueue());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                firstComeFirstServed.processRunning();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                updateRunningTime(firstComeFirstServed.getRunningQueue());
                finishedView(firstComeFirstServed.getFinishedQueue(),firstComeFirstServed.getRunningQueue());
                loadTable(firstComeFirstServed.getReadyQueue(), firstComeFirstServed.getFinishedQueue());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        //每秒执行一次
        scheduledThreadPoolExecutor.scheduleAtFixedRate(runnable, 0, 1, TimeUnit.SECONDS);
    }

    public void RRScheduling(ArrayList<PCB> processes) {
        roundRobin.setProcesses(processes);
        roundRobin.setSize(processes.size());
        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(3);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                int time = roundRobin.getTime();
                timeText.setText(String.valueOf(time));
                roundRobin.scheduleReady();
                loadTable(roundRobin.getReadyQueue(), roundRobin.getFinishedQueue());
                readyView(roundRobin.getReadyQueue(),roundRobin.getRunningQueue());
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                roundRobin.scheduleRunning();
                removeReadyView(roundRobin.getRunningQueue());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                roundRobin.processRunning();
                try {
                    updateRunningTime(roundRobin.getRunningQueue());
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                roundRobin.scheduleRound();

                roundRobin.scheduleFinish();
                finishedView(roundRobin.getFinishedQueue(),roundRobin.getRunningQueue());

                loadTable(roundRobin.getReadyQueue(), roundRobin.getFinishedQueue());
                try {
                    updateRunningTime(roundRobin.getRunningQueue());
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        //每秒执行一次
        scheduledThreadPoolExecutor.scheduleAtFixedRate(runnable, 0, 1, TimeUnit.SECONDS);
    }

    private void readyView(PCBQueue ready,PCBQueue runningQueue) {
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                PCB h = ready.peek();
                while (h != null) {
                    //已添加过的标记
                    boolean tag = false;
                    Circle circle = ViewUtil.createCircle();
                    String id = String.valueOf(h.getPid());
                    circle.setId(id);
                    for (int i = 0; i < readyBox.getChildren().size(); i++) {
                        if (readyBox.getChildren().get(i).getId().equals(id)) {
                            tag = true;
                        }
                    }
                    if (!tag) {
                        if (tagMap.get(h.getPid()) == null) {
                            ViewUtil.setColor(circle);
                            addMsg(h, (Color) circle.getFill());
                            tagMap.put(h.getPid(), (Color) circle.getFill());
                        }
                        else {
                            circle.setFill(tagMap.get(h.getPid()));
                        }

                        readyBox.getChildren().add(circle);

                    }
                    h = h.getNext();
                }

                if (runningQueue.peek() == null) {
                    runningCircle.setVisible(false);
                }
            }
        });
    }

    private void removeReadyView(PCBQueue runningQueue) {
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                PCB h = runningQueue.peek();
                if (h != running && h != null) {
                    //有新的进程开始执行
                    Circle circle = (Circle) readyBox.getChildren().get(0);
                    runningCircle.setFill(circle.getFill());
                    moveToRunning(circle,runningQueue);
                    running = h;
                }
            }
        });

    }

    private void moveToRunning(Circle circle,PCBQueue runningQueue) {
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                Path path = new Path();
                path.getElements().add(new MoveTo(circle.getCenterX(), circle.getCenterY()));
                double moveToX = (centerImg.getLayoutX() + centerImg.getFitWidth() / 2) + 40;
                double moveToY = (centerImg.getLayoutY() + centerImg.getFitHeight() / 2) + 40;
                path.getElements().add(new CubicCurveTo(200, 300, 300, 200, moveToX, moveToY));
                //path.getElements().add(new CubicCurveTo());
                PathTransition pathTransition = new PathTransition();
                pathTransition.setDuration(Duration.millis(1000));
                pathTransition.setPath(path);
                pathTransition.setNode(circle);
                pathTransition.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
                pathTransition.setCycleCount(1);
                pathTransition.setAutoReverse(false);
                pathTransition.setOnFinished(new EventHandler<ActionEvent>() {
                    @Override
                    public void handle(ActionEvent actionEvent) {
                        readyBox.getChildren().remove(0);
                        PCB h = runningQueue.peek();
                        if (h != null) {
                            runningCircle.setVisible(true);
                        }
                    }
                });
                pathTransition.play();
            }
        });

    }

    private void moveToFinished(PCBQueue runningQueue) {
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                runningCircle.setVisible(false);
                Path path = new Path();
                Circle circle = ViewUtil.createCircle();
                circle.setFill(runningCircle.getFill());
                mainView.getChildren().add(circle);
                circle.setLayoutX(-430);
                circle.setLayoutY(380);
                double moveToX = finishedBox.getLayoutX();
                double moveToY = finishedBox.getLayoutY();
                path.getElements().add(new MoveTo(moveToX, moveToY));
                path.getElements().add(new CubicCurveTo(800, -600, 200, 1200, 1460, 100));
                //path.getElements().add(new CubicCurveTo());
                PathTransition pathTransition = new PathTransition();
                pathTransition.setDuration(Duration.millis(1000));
                pathTransition.setPath(path);
                pathTransition.setNode(circle);
                pathTransition.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
                pathTransition.setCycleCount(1);
                pathTransition.setAutoReverse(false);
                pathTransition.setOnFinished(new EventHandler<ActionEvent>() {
                    @Override
                    public void handle(ActionEvent actionEvent) {
                        mainView.getChildren().remove(circle);
                        Circle circle = ViewUtil.createCircle();
                        circle.setFill(runningCircle.getFill());
                        finishedBox.getChildren().add(circle);
                        PCB h = runningQueue.peek();
                        if (h != null) {
                            runningCircle.setVisible(true);
                        }
                    }
                });
                pathTransition.play();
            }
        });
    }

    private void finishedView(PCBQueue finished,PCBQueue runningQueue) {
        int size = finished.getSize();
        PCB h = finished.peek();
        //找到最新完成的进程
        for (int i = 0; i < size - 1; i++) {
            h = h.getNext();
        }
        if (finish != h && h != null) {
            System.out.println("finished---");
            moveToFinished(runningQueue);
            finish = h;
        }
    }

    private void addMsg(PCB pcb, Color color) {
        Label msgLabel = new Label();
        msgLabel.setBackground((new Background(new BackgroundFill(color, null, null))));
        msgLabel.setText(" " + pcb.getPid() + " ");
        msgBox.getChildren().add(msgLabel);
    }

    private void revolveView(Node node) {
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                RotateTransition rotateTransition = new RotateTransition(Duration.seconds(100), node);
                rotateTransition.setFromAngle(0);
                rotateTransition.setToAngle(36000);
                //循环次数 Timeline.INDEFINITE = -1为无线循环
                rotateTransition.setCycleCount(-1);
                //是否自动改变旋转方向
                rotateTransition.setAutoReverse(false);
                rotateTransition.play();
                Timeline timeline = new Timeline();
                timeline.getKeyFrames().addAll(new KeyFrame(new Duration(0), new KeyValue(node.rotateProperty(), 0)));
                timeline.play();
            }
        });
    }

    private void loadTable(PCBQueue readyQueue, PCBQueue finishQueue) {
        ObservableList<PCB> ready = FXCollections.observableArrayList();
        PCB pointer = readyQueue.peek();
        while (pointer != null) {
            ready.add(pointer);
            pointer = pointer.getNext();
        }
        pidCol.setCellValueFactory(new PropertyValueFactory<PCB, Integer>("pid"));
        arriveTimeCol.setCellValueFactory(new PropertyValueFactory<PCB, Integer>("arriveTime"));
        needTimeCol.setCellValueFactory(new PropertyValueFactory<PCB, Integer>("needTime"));
        countCol.setCellValueFactory(new PropertyValueFactory<PCB, Integer>("count"));
        stateCol.setCellValueFactory(new PropertyValueFactory<PCB, String>("state"));
        pcbTableView.setItems(ready);

        ObservableList<PCB> finish = FXCollections.observableArrayList();
        PCB pointer2 = finishQueue.peek();
        while (pointer2 != null) {
            finish.add(pointer2);
            pointer2 = pointer2.getNext();
        }
        pidCol1.setCellValueFactory(new PropertyValueFactory<PCB, Integer>("pid"));
        arriveTimeCol1.setCellValueFactory(new PropertyValueFactory<PCB, Integer>("arriveTime"));
        needTimeCol1.setCellValueFactory(new PropertyValueFactory<PCB, Integer>("needTime"));
        countCol1.setCellValueFactory(new PropertyValueFactory<PCB, Integer>("count"));
        stateCol1.setCellValueFactory(new PropertyValueFactory<PCB, String>("state"));
        pcbTableView1.setItems(finish);
    }

    private void updateRunningTime(PCBQueue runningQueue) {
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                PCB p = runningQueue.peek();
                if (p != null) {
                    needTimeText.setText("进程结束剩余时间：" + p.getNeedTime());
                }
                else {
                    needTimeText.setText("进程结束剩余时间：0");
                }
            }
        });
    }

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        processController = this;
    }

}
